import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * This class reads from a trace file and simulates
 * traffic by sending UDP packets based on the arrival times and packet
 * sizes in the trace file. It adds 4 bytes of an auto-incremented
 * sequence number at the beginning of each UDP packet payload. Expected
 * input trace file format should be as per the below example:
 * SeqNo\tTime(in microsecond)\tSize(in Bytes) 1\t273\t30 2\t934\t99
 * 3\t2293\t27
 */
public class Sender extends Thread {
	
	private int port;
	private int recvPort;
	private String traceFile;
	private String logFile;

	public Sender(int port, int recvPort, String traceFile, String logFile)
	{
		this.port = port;
		this.recvPort = recvPort;
		this.traceFile = traceFile;
		this.logFile = logFile;
	}

	public void run() {
		try
		{
			System.out.println("Starting sender to send to receiver port " + recvPort);
			InetAddress addr = InetAddress.getByName("localhost");
			Scanner scanner = new Scanner(new File(traceFile));

			// Scanning the header line - skip this line from trace file
			scanner.nextLine();

			// ArrayLists to store seqNos, timestamps and packet size
			List<Integer> seqList = new ArrayList<Integer>();
			List<Long> timeList = new ArrayList<Long>();
			List<Integer> lenList = new ArrayList<Integer>();

			// Read the seqNos, timestamps and packet size into respective lists
			while (scanner.hasNextLine()) {
				String line = scanner.nextLine();
				String[] parts = line.split("\\s+");
				try {
					int seqNo = Integer.parseInt(parts[0]);
					seqList.add(seqNo);
					long timeMicros = Long.parseLong(parts[1]);
					timeList.add(timeMicros);
					int len = Integer.parseInt(parts[2]);
					lenList.add(len);
				} catch (Exception e) {

					e.printStackTrace();
				}
			}

			// based on trace file read, create UDP packets and send
			int size = lenList.size();
			long prevTimeMicros = 0;
			long sendTimes[] = new long[size];
			for (int i = 0; i < size; i++) {
				long timeMicros = timeList.get(i);
				int len = lenList.get(i);
				int seq = seqList.get(i);
				// Set minimum packet size to be 8 to piggyback seq no and port
				// number in the packet
				if (len < 8)
					len = 8;
				// Allocate buffer of size packet size
				ByteBuffer byteBuffer = ByteBuffer.allocate(len);
				byteBuffer.putInt(seq);
				byteBuffer.putInt(recvPort);
				byteBuffer.rewind();
				DatagramPacket packet = new DatagramPacket(byteBuffer.array(), len,
						addr, port);
				DatagramSocket socket = new DatagramSocket();
				System.out.println("Sending packet "+seq+"...");

				socket.send(packet);
				// Recording timestamp for each packet sent
				sendTimes[i] = System.nanoTime();
				socket.close();

				// Calculating the sleep time from trace file timestamps in order to
				// simulate the same departure rate
				long delay = (prevTimeMicros != 0 ? (timeMicros - prevTimeMicros)
						: 0);
				prevTimeMicros = timeMicros;
				TimeUnit.MICROSECONDS.sleep(delay);
			}

			// Writing out the sender logfile containing the sequence number and
			// sent timestamps
			File sendLog = new File(logFile);
			PrintWriter out = new PrintWriter(new FileWriter(sendLog));
			for (int i = 0; i < size; i++) {
				out.println(seqList.get(i) + "," + sendTimes[i] + "," + lenList.get(i));				
			}
			out.close();
			System.out.println("Ending sender to send to receiver port " + recvPort);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
	}

	public static void main(String[] args) throws Exception,
			InterruptedException {
		if (args.length != 4) {
			System.err
					.println("Usage: java Sender trafficShaperPortNo receiverPortNo traceFile senderLogFile");
			return;
		}

		int port = Integer.parseInt(args[0]);
		int recvPort = Integer.parseInt(args[1]);
		String traceFile = args[2];
		String logFile = args[3];
		
		Sender sender = new Sender(port, recvPort, traceFile, logFile);
		sender.start();
	}
}
